_G.global = {}

local function _find(this, value)
	for k, v in pairs(this) do
		if v == value then return k end
	end
end

local function _fmtstring(data)
	local visited = {}
	local function dump(data, prefix)
		local str = tostring(data)
		if _find(visited, data) ~= nil then return str end
		table.insert(visited, data)

		local prefix_next = prefix .. "  "
		str = str .. "\n" .. prefix .. "{"
		for k,v in pairs(data) do
			str = str .. "\n" .. prefix_next
			if type(k) == "string" then
				str = str .. "[\"" .. k .. "\"]="
			else
				str = str ..  "[" .. tostring(k) .. "]="
			end
			if type(v) == "table" then
				str = str .. dump(v, prefix_next)
			elseif type(v) == "string" then
				str = str .. "\"" .. v .. "\""
			else
				str = str .. tostring(v)
			end
		end
		str = str .. "\n" .. prefix .. "}"
		return str
	end
	return dump(data, "")
end

local function _stringsplit(input, delimiter)
    input = tostring(input)
    delimiter = tostring(delimiter)
    if (delimiter=='') then return false end
    local pos,arr = 0, {}
    -- for each divider found
    for st,sp in function() return string.find(input, delimiter, pos, true) end do
        table.insert(arr, string.sub(input, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(input, pos))
    return arr
end

local function _stringtrim(input)
    input = string.gsub(input, "^[ \t\n\r]+", "")
    return string.gsub(input, "[ \t\n\r]+$", "")
end

local _log = function(...)
	do return end
    local traceback = _stringsplit(debug.traceback("", 2), "\n")
    print("log from: " .. _stringtrim(traceback and traceback[4] or ""))
	print(...)
end

local _logt = function(t, desc)
    local str = desc and desc.. " = " or ""
	if type(t) ~= "table" then
		_log(str..(tostring(t) or "nil"))
	else
		_log(str.._fmtstring(t))
	end
end

-- elimilate global vars warnning
_G.vim = _G.vim or vim

local uv = vim.loop
local path_sep = uv.os_uname().version:match "Windows" and "\\" or "/"

local _join_paths = function(...)
    local result = table.concat({ ... }, path_sep)
    return result
end

local _require_clean = function(m)
    package.loaded[m] = nil
    _G[m] = nil
    local _, module = pcall(require, m)
    return module
end

local _require_safe = function(mod)
    local status_ok, module = pcall(require, mod)
    if not status_ok then
        local trace = debug.getinfo(2, "SL")
        local shorter_src = trace.short_src
        local lineinfo = shorter_src .. ":" .. (trace.currentline or trace.linedefined)
        local msg = string.format("%s : skipped loading [%s]", lineinfo, mod)
        _log(msg)
    end
    return module
end

local function _assign(old, new, k)
	local otype = type(old[k])
	local ntype = type(new[k])
	-- print("hi")
	if (otype == "thread" or otype == "userdata") or (ntype == "thread" or ntype == "userdata") then
		vim.notify(string.format("warning: old or new attr %s type be thread or userdata", k))
	end
	old[k] = new[k]
end

local function _replace(old, new, repeat_tbl)
	if repeat_tbl[old] then
	  	return
	end
	repeat_tbl[old] = true

	local dellist = {}
	for k, _ in pairs(old) do
		if not new[k] then
			table.insert(dellist, k)
		end
	end
	for _, v in ipairs(dellist) do
	  	old[v] = nil
	end

	for k, _ in pairs(new) do
		if not old[k] then
			old[k] = new[k]
		else
			if type(old[k]) ~= type(new[k]) then
				_log(string.format("Reloader: mismatch between old [%s] and new [%s] type for [%s]", type(old[k]), type(new[k]), k))
				_assign(old, new, k)
			else
				if type(old[k]) == "table" then
					_replace(old[k], new[k], repeat_tbl)
				else
					_assign(old, new, k)
				end
			end
		end
	end
end

local _reload = function(mod)
	if not package.loaded[mod] then
	  return _require_safe(mod)
	end

	local old = package.loaded[mod]
	package.loaded[mod] = nil
	local new = _require_safe(mod)

	if type(old) == "table" and type(new) == "table" then
	  local repeat_tbl = {}
	  _replace(old, new, repeat_tbl)
	end

	package.loaded[mod] = old
	return old
end

-- .local default
local _get_runtime_dir = function()
	return vim.call("stdpath", "data")
end

-- .config default
local _get_config_dir = function()
	return vim.call("stdpath", "config")
end

-- .cache default
local _get_cache_dir = function()
	return vim.call("stdpath", "cache")
end

local _is_file = function(path)
	local stat = uv.fs_stat(path)
	return stat and stat.type == "file" or false
  end

local _is_directory = function(path)
	local stat = uv.fs_stat(path)
	return stat and stat.type == "directory" or false
end

local _isempty = function(s)
	return s == nil or s == ""
end

-- export
global.log = _log
global.logt = _logt
global.require_safe = _require_safe
global.require_clean = _require_clean
global.reload = _reload
global.join_paths = _join_paths
global.is_file = _is_file
global.isempty = _isempty
global.is_directory = _is_directory
global.get_runtime_dir = _get_runtime_dir
global.get_config_dir = _get_config_dir
global.get_cache_dir = _get_cache_dir
global.runtime_dir = _get_runtime_dir()
global.config_dir = _get_config_dir()
global.cache_dir = _get_cache_dir()
global.pack_dir = _join_paths(global.runtime_dir, "site", "pack")
global.packer_install_dir = _join_paths(global.runtime_dir, "site", "pack", "packer", "start", "packer.nvim")
global.packer_cache_path = _join_paths(global.config_dir, "plugin", "packer_compiled.lua")


